﻿using System;
using System.Collections.Specialized;
using System.Data.SqlClient;
using System.Dynamic;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using HtmlAgilityPack;

namespace DictNS
{
  public partial class dict : Form
  {

    #region [Private members]
    private List<HtmlElement> logBuffer = new List<HtmlElement>();
//    private StringBuilder logBuffer = new StringBuilder();
    private WebClient cli;
    private List<String> userWords;
    private List<String> wordsFirstRequested = new List<string>();
    private Int32 rtbLinesCounter = 0;
    private bool isBrowserReady = false;
    public enum LogTextType { Info=0, Sucess=1, Error=2 }
    public struct userProxy
    {
      public String pUrl;
      public Int32 pPort;
      public String pUser;
      public String pPass;
      public String pType;
      public bool pIsUserDefined;
    }
    private userProxy _userDefinedProxy =  new userProxy();
    
    #endregion

    #region [Constructors]
    public dict()
    {
      InitializeComponent();
      #if DEBUG
      logIt("InitializeComponent passed.");
      #endif
      GetUserProxy();
      #if DEBUG
      logIt("User defined proxy read.");
      #endif
      InitializeWebClient();
      #if DEBUG
      logIt("InitializeWebClient finished.");
      #endif
      InitializeGUI();
      #if DEBUG
      logIt("InitializeGUI finished.");
      #endif
    }

    #endregion

    #region [Initialization Section]
    /// <summary>
    /// Restore path saved into AppSetting during previous session or creates AppSettings if not exists.
    /// Value will be the place where words are saved into from
    /// </summary>
    private void InitializeGUI()
    {
//      beautifyBrowserHtml();
      Version v = Assembly.GetExecutingAssembly().GetName().Version;
      Text = String.Format("{0} {1}.{2}.{3}", Text, v.Major, v.Minor, v.Build);
      toolStripProgressBar1.Visible = false;
      chkBxProxy.Checked = DictUtils.GetAppSetting("useSysProxy").Equals("true", StringComparison.InvariantCultureIgnoreCase);
      chkBxSound.Checked = DictUtils.GetAppSetting("playSound").Equals("true", StringComparison.InvariantCultureIgnoreCase);
      #if DEBUG
      logIt(String.Format("{0} fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      InitializePath();
      InitializeWords();
      InitializeSE();
    }

    /// <summary>
    /// Loads search engins and sets default one for use.
    /// </summary>
    private void InitializeSE()
    {
      int idx =0; 
      String se = DictUtils.GetAppSetting("se");
      if (String.IsNullOrEmpty(se)) { return; }
      List<String> seItems = se.Split('@').ToList<String>();
      String defaultSE = DictUtils.GetAppSetting("defaultSE");
      if (! String.IsNullOrEmpty(defaultSE)) {  Int32.TryParse(defaultSE, out idx);  }
      cbSE.DataSource = seItems;
      cbSE.SelectedIndex = idx;
    }
    
    /// <summary>
    /// Prepare list with stored words
    /// </summary>
    private async void InitializeWords()
    {
      if(!Directory.Exists(cbPath.Text)) { return;  }
      cbWordsList.DataSource = null;
      List<String> _wordsList = new List<string>();
      DirectoryInfo dir = new System.IO.DirectoryInfo(cbPath.Text);
      FileInfo[] fileList = await Task.Run(() => { return dir.GetFiles("*.mp3", SearchOption.TopDirectoryOnly); }  );
      foreach (FileInfo f in fileList)
      {
        _wordsList.Add(f.Name.Replace(".mp3", "").Replace("_", " "));
      }
      String _words = cbWordsList.Text ?? String.Empty;
      cbWordsList.DataSource = _wordsList;
      cbWordsList.AutoCompleteSource = AutoCompleteSource.ListItems;
      cbWordsList.AutoCompleteMode = AutoCompleteMode.Suggest;
      cbWordsList.SelectedIndex = -1;
      if(!String.IsNullOrEmpty(_words)){  
        cbWordsList.Text = _words;
        cbWordsList.SelectAll();
      }
      cbWordsList.Focus();
    }
    
    /// <summary>
    /// Prepare list with last used storage paths
    /// </summary>
    private void InitializePath()
    {
      if(DictUtils.GetAppSetting("cbPathHistoryAllowed", "true").ToLower().Equals("false")) {  return;  } // history disabled
      String _userPath = DictUtils.GetAppSetting("userPath");
      if(String.IsNullOrEmpty(_userPath)) { return; }
      List<String> _pathList = _userPath.Split(';').ToList<String>();
      Int32 _activeUserPath = 0;
      Int32.TryParse(DictUtils.GetAppSetting("activeUserPath", "0"), out _activeUserPath);
      cbPath.DataSource = _pathList;
      cbPath.AutoCompleteSource = AutoCompleteSource.ListItems;
      cbPath.AutoCompleteMode = AutoCompleteMode.Suggest;
      cbPath.SelectedIndex = _activeUserPath < 0 ? 0 : _activeUserPath;
    }
    
    /// <summary>
    /// Prepare WebClient for web comunication
    /// </summary>
    private void InitializeWebClient()
    {
      #if DEBUG
      logIt(String.Format("{0} fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      cli = new WebClient();
      if (cli == null) { throw new ApplicationException(String.Format("{0}: Failed to create WebClient!", (new StackTrace(true).GetFrame(0).GetMethod().Name))); }
      cli.DownloadDataCompleted += WebClient_DownloadDataCompleted;
      cli.DownloadFileCompleted += WebClient_DownloadFileCompleted;
      cli.DownloadProgressChanged += webClient_ProgressChanged;
      //cli.Credentials = CredentialCache.DefaultCredentials;
    }

    /// <summary>
    /// On each 'Go' button click initialize connection because settings might be changed
    /// </summary>
    private void InitializeConnection()
    {
      #if DEBUG
      logIt(String.Format("{0} fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      InitializeProxy();
      cli.Headers.Add("user-agnet", DictUtils.GetAppSetting("userAgent"));
      if (!_userDefinedProxy.pIsUserDefined) { return; } // no user defined proxy - user system settings
      cli.Headers.Add("Proxy-Authorization",
                      string.Format("Basic {0}",
                                    Convert.ToBase64String(Encoding.ASCII.GetBytes(String.Format("{0}:{1}",
                                                                                                 _userDefinedProxy.pUser, _userDefinedProxy.pPass)))));
    }

    /// <summary>
    /// prepare the proxy (if any) to work with
    /// </summary>
    private void InitializeProxy()
    {
      #if DEBUG
      logIt(String.Format("{0} fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      if (DictUtils.GetAppSetting("useSysProxy", "false").Equals("true", StringComparison.InvariantCultureIgnoreCase))
      {
        cli.Proxy = WebRequest.GetSystemWebProxy();// :  ProxySetUp();
        cli.Proxy.Credentials = CredentialCache.DefaultCredentials;
        #if DEBUG
        logIt("Proxy set to System Proxy");
        #endif
        return;
      }
      cli.Proxy = ProxySetUp();
      cli.UseDefaultCredentials = false;
      cli.Credentials = cli.Proxy.Credentials;
      #if DEBUG
      logIt(String.Format("Will user defined proxy [{0}:{1}].", _userDefinedProxy.pUrl, _userDefinedProxy.pPort));
      #endif
    }

    private IWebProxy ProxySetUp()
    {
      #if DEBUG
      logIt(String.Format("{0} fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      logIt(String.Format("Trying to set proxy to {0}", DictUtils.GetAppSetting("defaultProxy")));
      #endif
      if(String.IsNullOrEmpty(_userDefinedProxy.pUrl)){
        #if DEBUG
        logIt(String.Format("No valid proxy found [{0}]", _userDefinedProxy.pUrl));
        #endif
        return new WebProxy() as IWebProxy;
      }
      WebProxy p = null;
      try {  p = new WebProxy(_userDefinedProxy.pUrl, _userDefinedProxy.pPort); }
      catch (Exception ex) {
        MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        return new WebProxy() as IWebProxy;
      }
      p.UseDefaultCredentials = false;
      p.Credentials = new NetworkCredential(_userDefinedProxy.pUser, _userDefinedProxy.pPass);
      return p as IWebProxy;
    }

    #endregion
    
    #region [Outer Processes]
    
    void RtbLog_LinkClicked(object sender, LinkClickedEventArgs e)
    {
      System.Diagnostics.Process.Start(e.LinkText);
      cbWordsList.Focus();
      cbWordsList.SelectAll();
    }
    
    #endregion

    #region [Web Client handlers]

    private void webClient_ProgressChanged(object sender, DownloadProgressChangedEventArgs e)
    {
      toolStripProgressBar1.Value = e.ProgressPercentage;
    }

    /// <summary>
    /// event handler for WebClient DownloadDataCompleted event;
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void WebClient_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
    {
      String searchableWord;
      String _userState = e.UserState as string;
      String word = _userState.Contains("@") ? _userState.Split(new char[] {'@'})[0] : _userState;
      String _url = _userState.Contains("@") ? _userState.Split(new char[] {'@'})[1] : String.Empty;
      String currentPath = GetPath();
#if DEBUG
logIt(String.Format("{0} fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
#endif
      if (e.Error != null)
      {
        logIt(String.Format("Error: {0}", e.Error.Message), LogTextType.Error);
        UpdateStatus(e.Error.Message);
        ResetButtons();
        goto DataCompleted_End;
      }
      if (e.Cancelled)
      {
        logIt(String.Format("{0}: Operation canceled.", (new StackTrace(true).GetFrame(0).GetMethod().Name)), LogTextType.Error);
        UpdateStatus(String.Format("Requiest for [{0}] canceled.", word));
        ResetButtons();
        goto DataCompleted_End;
      }

      try  {  searchableWord = Regex.Replace(word, @"[\s\-]{1,}", "_");    }
      catch (Exception ex)
      {
        MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        logIt(String.Format("{0}: {1} - {2}", (new StackTrace(true).GetFrame(0).GetMethod().Name), ex.Message, (ex.InnerException == null ? String.Empty : ex.InnerException.Message)));
        goto DataCompleted_End;
      }
      #if DEBUG
      logIt(String.Format("Searching for {0} completed.", word));
      logIt(String.Format("Searching for {0}*.mp3 sound file . . .", word));
      #endif
//      else {  wordsFirstRequested.RemoveAt(wordsFirstRequested.IndexOf(word));  }
      Word oWord = (new Parsers(wordsFirstRequested.IndexOf(word) < 0)).doParseHtml(Encoding.ASCII.GetString((byte[])e.Result), word, cbSE.SelectedValue.ToString());
      if(wordsFirstRequested.IndexOf(word) < 0) {  wordsFirstRequested.Add(word);  }
//      if(((IDictionary<String, Object>)oWord).ContainsKey("newUrl"))
//      {
        if(! String.IsNullOrEmpty(oWord.NewUrl)) 
        {
          cli.DownloadDataAsync(new Uri(oWord.NewUrl), String.Format("{0}@{1}", word, oWord.NewUrl));
          logIt(String.Format("[{0} {1}] has been chosen.", word, oWord.PartOfSpeech));
          UpdateStatus(String.Format("[{0} {1}] has been chosen.", word, oWord.PartOfSpeech));
          goto DataCompleted_End;
        }
//      }
//      if(((IDictionary<String, Object>)oWord).ContainsKey("error"))
//      {
        if(!String.IsNullOrEmpty(oWord.Error))
        {
          logIt(String.Format("Error: {0}.", oWord.Error));
          UpdateStatus(String.Format("Error: {0}.", oWord.Error));
          goto DataCompleted_End;
        }
//      }
//      if(!((IDictionary<String, Object>)oWord).ContainsKey("mp3Url") && String.IsNullOrEmpty(oWord.Mp3Url.ToString()))
      if(String.IsNullOrEmpty(oWord.Mp3Url))
      {
        logIt(String.Format("[{0}] not found at {1}.", word, _url), LogTextType.Error);
        UpdateStatus(String.Format("[{0}] not found.", word));
        goto DataCompleted_End;
      }
      String fileName = MakeFileNameValid(String.Format("{0}{1}_{2}.mp3", currentPath, Regex.Replace(word, @"\s{1,}", "_"), oWord.PartOfSpeech.Trim()));
      try
      {
        toolStripProgressBar1.Value = 0;
        toolStripProgressBar1.Visible = true;
#if DEBUG
logIt("Checking file name . . .");
#endif
        if (!this.hasWriteAccess(cbPath.Text)) { throw new IOException(String.Format("No write access to {0}.", cbPath.Text));   }  // check write permission before downloading file
        if(File.Exists(fileName)) 
        {
          SuccessWordDownload(fileName);
          goto DataCompleted_End;
        }
#if DEBUG
logIt(String.Format("Sound for [{0}] found. Trying to download . . .", word));
#endif
        UpdateStatus(String.Format("Sound for [{0}] found. Trying to download . . .", word));
        cli.DownloadFileAsync(new Uri(oWord.Mp3Url), fileName, fileName);
#if DEBUG
logIt("Download started . . .");
#endif
        return;
      }
      catch (Exception ex)
      {
        toolStripProgressBar1.Visible = false;
        MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        #if DEBUG
        logIt(String.Format("Error! {0} - {1}", ex.Message, (ex.InnerException == null ? String.Empty : ex.InnerException.Message)));
        #endif
        goto DataCompleted_End;
      }
    DataCompleted_End:
      ResetButtons();
      GetWord();
    }

    /// <summary>
    /// event handler for WebClient DownloadFileCompleted event;
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void WebClient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
    {
#if DEBUG
logIt(String.Format("{0} fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
#endif
      if(e.Error != null)
      {
        logIt(e.Error.Message, LogTextType.Error);
        return;
      }
      string resWord = e.UserState as string;
      if(wordsFirstRequested.IndexOf(resWord) > -1) {  wordsFirstRequested.RemoveAt(wordsFirstRequested.IndexOf(resWord));  }
      SuccessWordDownload(resWord);
#if DEBUG
logIt("Checking for more words to search for . . .");
#endif
      GetWord();
    }

/// <summary>
/// Save path where word is downloaded and log result status
/// </summary>
/// <param name="resWord">word that has been donwloaded</param>
    private void SuccessWordDownload(String resWord)
    {
      logIt(String.Format("Result saved at {0}", resWord), LogTextType.Sucess);
      logIt(String.Format("!{0}", resWord), LogTextType.Info);
      UpdateStatus(String.Format("Result saved at {0}", resWord));
      try
      {
        Clipboard.SetText(resWord);
        InitializeWords(); // regenerate word list
        #if DEBUG
        logIt(String.Format("File location [{0}] saved in the clipboard.", resWord));
        #endif
      }
      catch (Exception ex)
      {
        logIt(String.Format("{0} - {1}", ex.Message, (ex.InnerException == null ? String.Empty : ex.InnerException.Message)));
        UpdateStatus(String.Format("{0} - {1}", ex.Message, (ex.InnerException == null ? String.Empty : ex.InnerException.Message)));
      }
    }
    
    #endregion

    #region [Form methods]
    /// <summary>
    /// Save configuration setting on exit. If file does not exist will be created.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void dict_FormClosing(object sender, FormClosingEventArgs e)
    {
      DictUtils.SaveSettings();
      btnClearLog.PerformClick();
    }
    #endregion

    #region [ComboBox methods]

    /// <summary>
    /// When ENTER is pressed do next step
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void Cb_KeyDown(object sender, KeyEventArgs e)
    {
      ComboBox _cb = sender as ComboBox;
      switch (_cb.Name)
      {
        case "cbPath":
          if (e.KeyCode == Keys.Return)
          {
            if (cbPath.Items.Count == 0 || String.IsNullOrEmpty(cbPath.Text)) { btnFolder.PerformClick(); }
            cbWordsList.Focus();
          }
          break;
        case "cbWordsList":
          if (e.KeyCode == Keys.Return) { btnGo.PerformClick(); }
          break;
      }
    }

    
    /// <summary>
    /// When a text box has the focus - select all contents.
    /// </summary>
    private void cbEntered(object sender, EventArgs e)    {      (sender as ComboBox).SelectAll();    }

    /// <summary>
    /// Event handler for text boxes when foculs has been lost.
    /// </summary>
    /// <param name="sender"><seealso cref="System.Windows.Forms.TextBox"/> control fired event.</param>
    /// <param name="e"><seealso cref="System.EventArgs"/> parameters for this event.</param>
    private void userPathLeave(object sender, EventArgs e)
    {
      ComboBox cb = sender as ComboBox;
      if(String.IsNullOrEmpty(cb.Text)) {  return; } // if path is empty do not check
      if (!hasWriteAccess(cb.Text))
      {
        logIt(String.Format("No write access to {0}.", cb.Text), LogTextType.Error);
        MessageBox.Show(String.Format("[{0}] is not accessible!", cb.Text), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        cb.Focus();
        cb.SelectAll();
        return;
      }
#if DEBUG
      logIt(String.Format("write access to {0} - OK.", cb.Text));
#endif
      if(DictUtils.GetAppSetting("cbPathHistoryAllowed", "true").ToLower().Equals("false")) {  return;  } // history disabled
      AddToPathHistory(); // if history allowed - add text to
      InitializeWords();    // on each new path build words list
    }

    /// <summary>
    /// add currrent words to history, if it is allowed
    /// </summary>
//    void CbWordsList_Leave(object sender, EventArgs e)
//    {
//      ComboBox _cb = sender as ComboBox;
//      _cb.Text = _cb.Text.Trim().ToLower();
//      if(String.IsNullOrEmpty(_cb.Text)) { return; } /* there is no word to search for */
//      if(cbWordsList.DataSource == null) { return; } /* word list is not built yet */
//      if((cbWordsList.DataSource as List<String>).IndexOf(_cb.Text) > -1) { return; } /* word exists in the list */
////      (cbWordsList.DataSource as List<String>).Insert(0, _cb.Text); /* when word does not exist in the list - add it */
//      return;
//    }

    /// <summary>
    /// Save change of search engine as default
    /// </summary>
    /// <param name="sender"><seealso cref="System.Windows.Forms.TextBox"/> control fired event.</param>
    /// <param name="e"><seealso cref="System.EventArgs"/> parameters for this event.</param>
    void CbSESelectedIndexChanged(object sender, EventArgs e)
    {
      ComboBox cb = sender as ComboBox;
#if DEBUG
      String val = cb.SelectedValue.ToString().Split(new char[] {'/'}, StringSplitOptions.RemoveEmptyEntries)[1];
      logIt(String.Format("Default search engine set to {0}.", val));
#endif
      DictUtils.SetAppSetting("defaultSE", cb.SelectedIndex.ToString());
      cbWordsList.Focus();
    }

    #endregion

    #region [Button methods]

    /// <summary>
    /// Button opening the dialig for folder choice
    /// </summary>
    /// <param name="sender">the button clicked</param>
    /// <param name="e">event parameters - in the case - null</param>
    private void btnFolder_Click(object sender, EventArgs e)
    {
      #if DEBUG
      logIt(String.Format("{0} fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      //      if (!String.IsNullOrEmpty(tbPath.Text))
      if (!String.IsNullOrEmpty(cbPath.Text))
      {
        folderBrowserDialog1.SelectedPath = cbPath.Text;
      }
      if(folderBrowserDialog1.ShowDialog() == DialogResult.Cancel) { return;  }
      if(String.IsNullOrEmpty(folderBrowserDialog1.SelectedPath)) { return;  } /* no path selected */
      cbPath.Text = folderBrowserDialog1.SelectedPath;
      if (cbPath.Items.Count == 0 || cbPath.Text.Length > 0)  // if folder has been chosen go to next field
      {
        cbWordsList.Focus();
      }
      else { cbPath.Focus(); }  // else return back
      InitializeWords(); // build a new list from the new path
      AddToPathHistory(); // if history allowed - add text to
    }

    /// <summary>
    /// Start searching
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnGo_Click(object sender, EventArgs e)
    {
      cbWordsList.Text = cbWordsList.Text.Trim().ToLower();
      #if DEBUG
      logIt(String.Format("{0} fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      rtbLinesCounter = 0;
//      ctrLog.Document.Body.InnerHtml = String.Empty;
      if (String.IsNullOrEmpty(cbWordsList.Text))
      {
        MessageBox.Show("Please enter a word to search.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        return;
      }
      try  {  userWords = Regex.Replace(cbWordsList.Text, @"\s{0,},\s{0,}", ",").Split(new char[] { ',' }).ToList<String>();  }
      catch (Exception ex)
      {
        MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
      }
      #if DEBUG
      logIt("Trying to initiate connetion.");
      #endif
      InitializeConnection();
      #if DEBUG
      logIt(String.Format("Trying to get word{0} ...", (userWords.Count > 0 ? "s": String.Empty)));
      #endif
// main loop
      cbWordsList.Focus();
      cbWordsList.SelectAll();
      GetWord();
    }

    /// <summary>
    /// Stops current search
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnStop_Click(object sender, EventArgs e)
    {
      ResetButtons();
      cli.CancelAsync();
    }

    private void Buttons_MouseHover(object sender, EventArgs e)
    {
      Button btn = sender as Button;
      btn.Image = DictNS.Properties.Resource.settings12_active;
    }

    private void Buttons_MouseLeave(object sender, EventArgs e)
    {
      Button btn = sender as Button;
      btn.Image = DictNS.Properties.Resource.settings12_inactive;
    }

    private void btnSettings_Click(object sender, EventArgs e)
    {
      String _tag = Convert.ToString((sender as Button).Tag);
      frmSettings _settings = null;
      if (String.IsNullOrEmpty(_tag))   {  _settings = new frmSettings(); }
      else
      {
        _settings = new frmSettings("se");
        (sender as Button).Tag = null;
      }
      if (!(_settings is frmSettings))
      {
        MessageBox.Show("Failed to open Settings dialog", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
      }
      _settings.StartPosition = FormStartPosition.CenterParent;
      try
      {
        _settings.ShowDialog(this);
        InitializeSE();
        if (!_settings.IsDisposed) { _settings.Dispose(); }
      }
      catch (Exception ex)
      {
        MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
      }
    }
   
    void BtnClearLogClick(object sender, EventArgs e)
    {
      ctrLog.Document.Body.InnerHtml = String.Empty;
      (sender as Button).Enabled = false;
    }
    
    #endregion

    #region [CheckBox methods]
    
    private void ChkBxProxyCheckedChanged(object sender, System.EventArgs e)
    {
      DictUtils.SetAppSetting("useSysProxy", ((sender as CheckBox).Checked ? "true" : "false"));
      cbWordsList.Focus();
    }
    
    private void chkBxSoundCheckedChanged(object sender, System.EventArgs e)
    {
      DictUtils.SetAppSetting("playSound", ((sender as CheckBox).Checked ? "true" : "false"));
      cbWordsList.Focus();
    }
    
    #endregion

    #region [Helper methods]

    /// <summary>
    /// Start seach process
    /// </summary>
    private void GetWord()
    {
      #if DEBUG
      logIt(String.Format("method [{0}] fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      String currentPath = GetPath(); // choose where to store result
      String se = DictUtils.GetDefaultSearchEngine();
      if (String.IsNullOrEmpty(se))
      {
        btnSettings.Tag = "se";
        btnSettings.PerformClick();
        se = DictUtils.GetDefaultSearchEngine();
        if (String.IsNullOrEmpty(se)) {  return;  }
      }
      #if DEBUG
      logIt(String.Format("Path to store data: [{0}].", currentPath));
      logIt(String.Format("Words found: [{0}]{1}.", userWords.Count(), (userWords.Count() > 0 ? " - continue." : " - end of search.")));
      #endif
      btnGo.Enabled = userWords.Count == 0;
      if (userWords.Count == 0)
      {
        logIt("-");
        ResetButtons();
        return;
      }
      String word = userWords[0]; // extract left most word in the list
      userWords.RemoveAt(0); // delete it
      if(wordsFirstRequested.IndexOf(word) > -1) {  wordsFirstRequested.RemoveAt(wordsFirstRequested.IndexOf(word));  }
      #if DEBUG
      logIt(String.Format("Searching for: [{0}].", word));
      #endif
      String fileName = String.Format("{0}{1}.mp3", currentPath, Regex.Replace(word, @"\s{1,}", "_"));
      if (File.Exists(fileName))
      {
        if (MessageBox.Show(String.Format("{0} exists. Overwrite it?.", fileName), AppDomain.CurrentDomain.FriendlyName, MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No)
        {
          Clipboard.SetText(fileName);  // put file path in clipboard anyway
          logIt(String.Format("{0} exists. Overrite canceled.", fileName));
          UpdateStatus(String.Format("{0} exists. Overrite canceled.", fileName));
          ResetButtons();
          return;
        }
        try { File.Delete(fileName); } 
        catch (Exception x) 
        {
          toolStripProgressBar1.Visible = false;
          MessageBox.Show(x.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
#if DEBUG
    logIt(String.Format("Error! {0}", x.Message));
#endif
          return;
        }
        finally {  ResetButtons();  }
      }
      UpdateStatus(String.Format("Searching for [{0}] . . .", word));
      try           // if this is direct sound file - download it
      {
        toolStripProgressBar1.Value = 0;
        toolStripProgressBar1.Visible = true;
        if (se.EndsWith("mp3"))
        {
          String w = Regex.Replace(word, @"\s{1,}", "_");
          String url = se.Replace("%s", w);
          Uri uri = new Uri(url);
          String userParam = String.Format("{0}{1}.mp3", currentPath, w, url);
          cli.DownloadFileAsync(uri, userParam, userParam);
        }
        else
        {
          String url  = se.Replace("%s", Regex.Replace(word, @"\s{1,}", "-"));
          cli.DownloadDataAsync(new Uri(url), String.Format("{0}@{1}", word, url));
        }
        btnStop.Enabled = true;
#if DEBUG
      logIt("Download started.");
#endif
      }
      catch (Exception ex)
      {
        toolStripProgressBar1.Visible = false;
        MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
#if DEBUG
logIt(String.Format("Error! {0} - {1}", ex.Message, (ex.InnerException == null ? String.Empty : ex.InnerException.Message)));
#endif
        return;
      }
    }

    /// <summary>
    /// return full path where result will be stored
    /// </summary>
    /// <returns>full path ending with a backslash</returns>
    private string GetPath()
    {
      string currentPath = (String.IsNullOrEmpty(cbPath.Text) ? Directory.GetCurrentDirectory() : cbPath.Text);
      currentPath += !currentPath.EndsWith("\\") ? "\\" : String.Empty;
      return currentPath;
    }

    /// <summary>
    /// Check word for OS reserved keywords and adds one (1) at the end it it is
    /// </summary>
    /// <param name="fileName">Full path where file have to be saved</param>
    /// <returns>Full path with corrected filename</returns>
    private String MakeFileNameValid(String fileName)
    {
      String[] _keyWords = new String[] { "con", "prn", "aux", "clock$", "nul", 
                                          "com0", "com1", "com2", "com3", "com4",
                                          "com5", "com6", "com7", "com8", "com9", 
                                          "lpt0", "lpt1", "lpt2", "lpt3", "lpt4",
                                          "lpt5", "lpt6", "lpt7", "lpt8", "lpt9" };
      String fileWithoutExt = Path.GetFileNameWithoutExtension(fileName);
      if(Array.IndexOf(_keyWords, fileWithoutExt) > -1) {
        fileName = Regex.Replace(fileName, fileWithoutExt, fileWithoutExt+"1");
      }
      return fileName.Replace("_.mp3", ".mp3"); // if no part of speach exists (i.e. plurals, irregulars, etc.)
    }
    
    /// <summary>
    /// Check for write permissions of the chosen folder
    /// </summary>
    /// <param name="_path"><seealso cref="System.String"/> with the path user has been entered.</param>
    /// <returns>true if can wirte; false - if cannot</returns>
    private bool hasWriteAccess(string _path)
    {
      PermissionSet perm;
      if (!Directory.Exists(_path))
      {
        try  {     Directory.CreateDirectory(_path);    }
        catch (Exception e)
        {
          DictUtils._dump(e);
          logIt(String.Format(String.Format("[{0}] {1}", _path, e.Message)), LogTextType.Error);
          UpdateStatus(String.Format("[{0}] {1}", _path, e.Message));
          return false;
        }
      }
      try
      {
        perm = new PermissionSet(PermissionState.None);
        perm.AddPermission(new FileIOPermission(FileIOPermissionAccess.Write, _path));
      }
      catch (Exception e)
      {
        DictUtils._dump(e);
        MessageBox.Show(e.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        UpdateStatus(String.Format("[{0}] {1}", _path, e.Message));
        return false;
      }
      return perm.IsSubsetOf(AppDomain.CurrentDomain.PermissionSet);
    }

    private HtmlElement makeElement(String tag = "div")
    {
      return ctrLog.Document.CreateElement(tag);
    }
    
    /// <summary>
    /// Appends Html element to document.body
    /// </summary>
    /// <param name="el"><seealso cref="System.String"/>html to append. If document is not ready yeat - store it into a buffer.</param>
    private void AppendElement(ref HtmlElement el)
    {
      if(!isBrowserReady)
      {
        logBuffer.Add(el);
        return;
      }
      if(logBuffer.Count > 0)
      {
        foreach (HtmlElement e in logBuffer) {  ctrLog.Document.Body.AppendChild(e);   }
        logBuffer = new List<HtmlElement>();
      }
      ctrLog.Document.Body.AppendChild(el);
    }
    
    private void showLog()
    {
      if(!isBrowserReady) { return;  }
      ctrLog.Document.Window.ScrollTo(0, ctrLog.Document.Body.ScrollRectangle.Height);
      btnClearLog.Enabled = !String.IsNullOrEmpty(ctrLog.Document.Body.InnerText);      
    }

    /// <summary>
    /// Show icons after sound file has been downloaded.
    /// </summary>
    /// <param name="icon"><seealso cref="System.String"/>string with name of bitmap resource to load</param>
    /// <param name="src"><seealso cref="System.String"/> full path where file has been downloaded to</param>
    HtmlElement makeIcon(String icon, String src)
    {
      #if DEBUG
      logIt(String.Format("method [{0}] fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      const int m_Width = 12;
      const int m_Height = 12;
      HtmlElement nbsp = makeElement("span");
      nbsp.InnerHtml = " &nbsp ";

      HtmlElement img = makeElement("img");
      img.Id = String.Format("btn{0}", icon);
      img.SetAttribute("data-location", src);
      img.SetAttribute("alt", String.Format("{0} location", icon));
      img.SetAttribute("src", String.Format("data:image/png;base64,{0}", getBase64Image(icon.ToLower())));
      img.Style = String.Format("width: {0}px; height: {1}px", m_Width, m_Height);
      nbsp.AppendChild(img);
      return nbsp;
//      return img;
    }
    
    /// <summary>
    /// Show icons after sound file has been downloaded.
    /// </summary>
    /// <param name="el"><seealso cref="Forms.HtmlElement"/>html element to put icons into</param>
    /// <param name="msg"><seealso cref="System.String"/> with the text containing file location</param>
    void AppendHtmlIcons(ref HtmlElement el, ref String msg)
    {
      #if DEBUG
      logIt(String.Format("method [{0}] fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      GroupCollection source =  Regex.Match(msg, @"([a-zA-Z]:\\.+mp3)").Groups;
      if(source[1] == null)  {  return;  }

      el.AppendChild(makeIcon("Copy", source[1].Value));
      el.AppendChild(makeIcon("Open", source[1].Value));
      el.AppendChild(makeIcon("Play", source[1].Value));
    }
    
    /// <summary>
    /// show result from the actions during search and retrieve process
    /// </summary>
    /// <param name="msg"><seealso cref="System.String"/> with the text that will be shown</param>
    /// <param name="_type"><seealso cref="LogTextType"/> enum with possible result values</param>
    public void logIt(String msg, LogTextType _type = LogTextType.Info)
    {
      HtmlElement el;
      String colr = (new String[] { "dimgray", "green", "red" })[(int)_type]; //Info=0, Sucess=1, Error=2
      if(msg.Equals("-"))   // just add a separator 
      {
        el = makeElement("hr");
      }
      else if(msg.ToLower().StartsWith("!", StringComparison.CurrentCulture) && msg.ToLower().EndsWith("mp3", StringComparison.CurrentCulture))
      {
        if(ctrLog.Document.Body.GetElementsByTagName("bgsound").Count == 0)
        {
          el = makeElement("bgsound");
          el.SetAttribute("autostart", "autostart");
        }
        else {  el = ctrLog.Document.Body.GetElementsByTagName("bgsound")[0];  }
        el.SetAttribute("src", String.Format("file:///{0}", msg.Replace("!", "")));
      } 
      else if(msg.ToLower().Contains("http"))
      {
        el = makeElement("a");
        el.Style +=String.Format("color:{0}; font-size:.8em;", colr);
        el.SetAttribute("href", msg.Trim());
        el.SetAttribute("target", "_blank");
        el.InnerText = msg.Trim();
      }
      else 
      {
        el = makeElement("div");
        el.Style +=String.Format("color:{0}; font-size:.8em;", colr);
        el.InnerText = String.Format("{0:000}. {1}", (++rtbLinesCounter), msg.Trim());
        if(msg.Trim().ToLower().StartsWith("result saved at", StringComparison.CurrentCulture))
        {
          AppendHtmlIcons(ref el, ref msg);
        }
      }
      AppendElement(ref el);
//      if(msg.Trim().ToLower().StartsWith("result saved at", StringComparison.CurrentCulture))  
//      {
//        GroupCollection source =  Regex.Match(msg, @"([a-zA-Z]:\\.+mp3)").Groups;
//        if(!String.IsNullOrEmpty(source[1].Value))
//        {
//          el = makeIcon("Copy", source[1].Value);
//          AppendElement(ref el);
//          el = makeIcon("Open", source[1].Value);
//          AppendElement(ref el);
//          el = makeIcon("Play", source[1].Value);
//          AppendElement(ref el);
//        }
////        AppendHtmlIcons(ref el, ref msg);
//      }
      showLog();
    }

    /// <summary>
    /// Show message in the statsu bar for the user.
    /// </summary>
    /// <param name="s">Message to show.</param>
    private void UpdateStatus(String s)
    {
      toolStripStatusLabel1.Text = s;
      statusStrip1.Refresh();
    }

    /// <summary>
    /// Restore initial state of the buttons
    /// </summary>
    private void ResetButtons()
    {
      btnStop.Enabled = false;
      btnGo.Enabled = true;
      toolStripProgressBar1.Visible = false;
      cbWordsList.Focus();
      cbWordsList.SelectAll();
    }

    /// <summary>
    /// Prepare user defined proxy data
    /// </summary>
    private void GetUserProxy()
    {
      #if DEBUG
      logIt(String.Format("method [{0}] fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      _userDefinedProxy.pIsUserDefined = DictUtils.GetAppSetting("useSysProxy").Equals("false", StringComparison.InvariantCultureIgnoreCase);
      if (!_userDefinedProxy.pIsUserDefined) { return;  }
      String[] _proxy = DictUtils.GetAppSetting("userProxy").Split('|');
      _userDefinedProxy.pUrl = _proxy[0];
      //_userDefinedProxy.pUrl = String.Format("{0}://{1}", (String.IsNullOrEmpty(_proxy[4]) ? "http" : _proxy[4]), _proxy[0]);
      if (!String.IsNullOrEmpty(_proxy[1])) { _userDefinedProxy.pPort = Convert.ToInt32(_proxy[1]); }
      _userDefinedProxy.pUser = _proxy[2];
      _userDefinedProxy.pPass = _proxy[3];
    }
    
    /// <summary>
    /// Add path entered by user if history enabled
    /// </summary>
    /// <param name="cb"></param>
    private void AddToPathHistory()
    {
      #if DEBUG
      logIt(String.Format("method [{0}] fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
    	if(String.IsNullOrEmpty(cbPath.Text)) { return;  }
    	string cbPathCurrentValue = cbPath.Text;
      int maxNumPaths = -1;
      Int32.TryParse(DictUtils.GetAppSetting("nudNumPaths", "9"), out maxNumPaths);
      bool isNewPathAdded = false;
      List<String> pathList = cbPath.DataSource as List<String>;
      if (pathList == null) {  pathList = new List<string>(maxNumPaths); }
    
      if (cbPath.Items.IndexOf(cbPathCurrentValue) == -1) {		// if text does not exists into collection
#if DEBUG
logIt(String.Format("{0} not found in the list.", cbPath.Text));
#endif
        cbPath.DataSource = null;
        if (pathList != null && pathList.Count == maxNumPaths) {
          pathList.RemoveAt(pathList.Count - 1);      // remove last if exceeds max number of paths
#if DEBUG
logIt(String.Format("Last entry removed from history because max entries allowed {0} has been hit.", maxNumPaths.ToString()));
#endif
        }
        pathList.Insert(0, cbPathCurrentValue);	//  add the new path first in the list
#if DEBUG
logIt("Path inserted at position 0.");
#endif
        cbPath.DataSource = pathList; // bind it back
        cbPath.SelectedIndex = 0;
        isNewPathAdded = true;
      }
      if (cbPath.SelectedIndex < 0) {
        cbPath.SelectedIndex = cbPath.Items.IndexOf(cbPathCurrentValue);
      }
      DictUtils.SetAppSetting("activeUserPath", cbPath.SelectedIndex.ToString());
      if (isNewPathAdded) {
      	DictUtils.SetAppSetting("userPath", ";" + Regex.Replace(String.Join(";", pathList.ToArray()), @";{2,}", ";"));
      }
#if DEBUG
logIt("Path history updated.");
#endif
    }

    /// <summary>
    /// Add some styles to the Html document when available
    /// </summary>
    private void beautifyBrowserHtml()
    {
      if(ctrLog.Document.Body == null) { return; }
      ctrLog.Document.Body.Style = "font-size:.7em;font-family: Verdana, Arial, Helvetica, sans-serif;";
    }
    
    /// <summary>
    /// Convert resource image into Base64 string.
    /// </summary>
    /// <param name="imgName">Name of the resource as a string</param>
    /// <returns>Base64 converted string</returns>
    private String getBase64Image(string imgName)
    {
      #if DEBUG
      logIt(String.Format("method [{0}] fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      switch(imgName.ToLower())
      {
        case "copy":
          byte[] imgArray = (byte[])(new ImageConverter()).ConvertTo(DictNS.Properties.Resource.copy,typeof(byte[]));
          return Convert.ToBase64String(imgArray);
        case "open":
          imgArray = (byte[])(new ImageConverter()).ConvertTo(DictNS.Properties.Resource.open,typeof(byte[]));
          return Convert.ToBase64String(imgArray);
        case "play":
          imgArray = (byte[])(new ImageConverter()).ConvertTo(DictNS.Properties.Resource.player_play,typeof(byte[]));
          return Convert.ToBase64String(imgArray);
      }
      return String.Empty;
//      byte[] imgArray = (byte[])(new ImageConverter()).ConvertTo(img,typeof(byte[]));
//      return Convert.ToBase64String(imgArray);
    }
    
    /// <summary>
    /// Check log message for file name. If found append Copy & Open buttons to the message
    /// </summary>
    /// <param name="msg">Message to be logged</param>
//    private void CheckForFile(ref String msg)
//    {
//      if(msg.StartsWith("!", StringComparison.CurrentCulture)) { return;  }
//      if(!Regex.IsMatch(msg, @"([a-zA-Z]:\\.+mp3)")) {  return;  }
//      GroupCollection source =  Regex.Match(msg, @"([a-zA-Z]:\\.+mp3)").Groups;
//      if(source[1] == null)  {  return;  }
//      const int m_Width = 12;
//      const int m_Height = 12;
//      String img1 = String.Format(@"&nbsp;<img id='btnCopy' data-location='{0}' 
//                                     alt='Copy location' src='data:image/png;base64,{1}'
//                                     width='{2}px' height=='{3}px' />"
//                                  ,source[1].Value, getBase64Image("copy"), m_Width, m_Height);
//      String img2 = String.Format(@"&nbsp;<img id='btnOpen' data-location='{0}' 
//                                     alt='Copy location' src='data:image/png;base64,{1}'
//                                     width='{2}px' height=='{3}px' />"
//                                  ,source[1].Value, getBase64Image("open"), m_Width, m_Height);
//      msg = String.Format("{0}{1}{2}{3}{4}{5}", msg, Environment.NewLine, 
//                          img1, Environment.NewLine, img2, Environment.NewLine);
//    }
    
    #endregion

    #region [WebBrowser]

    /// <summary>
    /// Attach click handler to Html document when ready
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void CtrLogDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
      isBrowserReady = true;
      ctrLog.Document.Click += HtmlDocument_Click;
    }
    
    /// <summary>
    /// Catch where mouse has been clicked. If this is a button - acts accordingly
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void HtmlDocument_Click(object sender, HtmlElementEventArgs e)
    {
      if(e.CtrlKeyPressed)
      {
        Clipboard.SetText(ctrLog.Document.Body.OuterHtml);
        UpdateStatus("Html document copied into clipboard.");
        return;
      }
      HtmlElement el = ctrLog.Document.GetElementFromPoint(e.ClientMousePosition);
      #if DEBUG
      logIt(String.Format("method [{0}] fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      if(el == null)   {  return;   }
      switch (el.Id) {
        case "btnCopy":
          Clipboard.SetText(el.GetAttribute("data-location"));
          UpdateStatus("Full path copied into clipboard.");
          break;
        case "btnOpen":
          UpdateStatus("Opening location . . .");
          OpenFileLocation(el.GetAttribute("data-location"));
          break;
        case "btnPlay":
          if(ctrLog.Document.Body.GetElementsByTagName("bgsound").Count == 0) {  break;  }
          HtmlElement sound = ctrLog.Document.Body.GetElementsByTagName("bgsound")[0];
          sound.SetAttribute("src", String.Format("file:///{0}", el.GetAttribute("data-location")));          
          break;
      }
      cbWordsList.Focus();
      cbWordsList.SelectAll();
    }
    
    /// <summary>
    /// Open folder where mo3 file has been written into
    /// </summary>
    /// <param name="path">File name (with full path)</param>
    private void OpenFileLocation(String path)
    {
      #if DEBUG
      logIt(String.Format("method [{0}] fired", (new StackTrace(true).GetFrame(0).GetMethod().Name)));
      #endif
      Process p = null;
      try {
        p = Process.Start((new ProcessStartInfo("Explorer.exe", String.Format("/Select, {0}", path))));
      } catch (Exception ex) {
        MessageBox.Show(String.Format("Error occurs: {0}{1}{0}{2}{0}", Environment.NewLine, ex.Message, 
                                      Environment.NewLine, (ex.InnerException != null ? ex.InnerException.Message : String.Empty)),
                                     Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
      }
      finally 
      {
        if(p != null) {  p.Dispose(); }
        UpdateStatus("Ready.");
      }
    }

    #endregion
    
   }
}
